/**
 * Collaborative Story Development Web Application (CSDApp) Copyright Anton
 * Strack 2014
 *
 * This file is part of Collaborative Story Development Application (CSDApp).
 *
 * CSDApp is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * CSDApp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * CSDApp. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package csdwa;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The task controller handles the end user's interaction with CRUD management
 * of tasks.
 *
 * @author Anton Strack
 */
public class TaskController extends ResourceController {

    private final String VIEW_LIST = "/task/index.jsp";
    private final String VIEW_VIEW = "/task/view.jsp";
    private final String VIEW_CREATE = "/task/create.jsp";
    private final String VIEW_UPDATE = "/task/update.jsp";
    private final String VIEW_DELTE = "/task/delete.jsp";

    public TaskController(Connection conn) {
        super(conn);
    }

    /**
     * This method primarily handles form related requests.
     *
     * @param request the request object
     * @return an updated request object sufficient to complete the request.
     */
    @Override
    public CsdwaRequest handlePostRequest(CsdwaRequest request) {
        String form = request.getFormId();
        String primaryAction = request.getPrimaryActionTypeName();
        String primaryResource = request.getPrimaryResourceTypeName();
        String postId = request.getParameter("secondaryId");
        request.setPrimaryResourceId(postId);
        int id = request.getPrimaryResourceId();
        if (form.isEmpty() == false) {
            request.setPrimaryResourceId(request.getParameter("postId"));
            if (form.equalsIgnoreCase("create-task")) {
                request.removeFormId();
                request = handleCreate(request);
            } else if (form.equalsIgnoreCase("update-task")) {
                request.removeFormId();
                request = handleUpdate(request);
            } else if (form.equalsIgnoreCase("delete-task")) {
                request.removeFormId();
                request = handleDelete(request);
            }
        }
        return request;
    }

    /**
     * This method handles get requests. It accepts the primaryResouce and
     * primaryAction as variables to allow routing from the handleGetRequest
     * method.
     *
     * @param request the request object
     * @return an updated request object sufficient to complete the request.
     */
    @Override
    public CsdwaRequest handleGetRequest(CsdwaRequest request) {
        String primaryAction = request.getPrimaryActionTypeName();
        String primaryResource = request.getPrimaryResourceTypeName();
        String secondaryResource = request.getSecondaryResourceTypeName();
        String secondaryAction = request.getSecondaryActionTypeName();
        int secondaryId = request.getSecondaryResourceId();
        int id = request.getPrimaryResourceId();
        if ("task".equalsIgnoreCase(primaryResource)) {
            if ("view".equalsIgnoreCase(primaryAction) && id != 0) {

                TaskDao tdao = new TaskDao(conn);
                tdao.setUseRelatedMarkers(true);
                int result = tdao.selectBeanById(id);
                request.setBean(tdao.getBean());
                if (result > 0) {
                    TaskManager taskManager = this.ResourceRequestDelegator.getTaskManager();
                    taskManager.trackTask(id, request.getCurrentUser().getId(), "view");
                    request.setView(VIEW_VIEW);
                    this.getResourceRequestDelegator().getUserHistoryManager().recordUserPostHistory(request.getCurrentUser(), InfoAggregator.getInstance().getActionTypeId("view"), tdao.getBean());
                } else {
                    request.setMessage("The task couldn't be found.");
                    request.setMessageType("error");
                    request.setPrimaryActionTypeName("list");
                    request.setPrimaryResourceTypeName("task");
                    request.setPrimaryResourceId(0);
                    request = handleGetRequest(request);
                }
            } else if ("update".equalsIgnoreCase(primaryAction) && id != 0) {

                TaskDao tdao = new TaskDao(conn);
                TaskActionDao tadao = new TaskActionDao(conn);
                tdao.selectBeanById(id);
                /* Store the bean in a session so that the it is available to the jsp page and also post update handler on post submit. This way it can compare the post bean values to the original bean values. Doing so will mean that only the columns changed will be sent to the database to be updated.
                 */

                // select task actions
                int taResult = tadao.selectBeans();
                Logger.getLogger(TaskController.class.getName()).log(Level.INFO, "taResult is:{0}", taResult);
                if (taResult > 0) {
                    request.setAttribute("actions", tadao.getBeans());
                    request.setView(VIEW_UPDATE);
                } else {
                    request.setMessage("The task couldn't be found.");
                    request.setMessageType("error");
                    request.setPrimaryActionTypeName("list");
                    request.setPrimaryResourceTypeName("task");
                    request.setPrimaryResourceId(0);
                    request = handleGetRequest(request);
                    Logger.getLogger(TaskController.class.getName()).log(Level.WARNING, "No action type was detected for tasks.");
                }

                request.removeSessionAttribute("bean");
                request.setSessionAttribute("bean", tdao.getBean());
            } else if ("create".equalsIgnoreCase(primaryAction)) {
                request.setView(VIEW_CREATE);

                UserDao udao = new UserDao(conn);
                udao.setMarker(QueryMarker.ORDER_BY_COLUMN, "username");
                udao.setMarker(QueryMarker.ORDER_BY_MODE, "DESC");

                ResourceTypeDao rtdao = new ResourceTypeDao(conn);
                TaskActionDao tadao = new TaskActionDao(conn);
                tadao.setMarker(QueryMarker.ORDER_BY_COLUMN, "name");
                tadao.setMarker(QueryMarker.ORDER_BY_MODE, "ASC");
                boolean correctResourceType = false;// if the resource type can't be determined, then the only available action is to create new as that doesn't require a resource id
                int resourceTypeId = 0;
                // select the users who don't have guest roles
                int uResult = udao.selectUsersByRole(3, false);
                Logger.getLogger(TaskController.class.getName()).log(Level.INFO, "Need a centralized source for ID numbers as udao.selectUsersByRole(3) shouldn't be using 'magic numbers'");
                if (uResult > 0) {
                    request.setAttribute("recipients", udao.getBeans());
                } else {
                    Logger.getLogger(TaskController.class.getName()).log(Level.SEVERE, "No users could be found to populate the recipient field");
                }
                // select resource type
                // select by the parent id as this will get post type resources
                rtdao.setMarker(QueryMarker.ORDER_BY_COLUMN, "name");
                rtdao.setMarker(QueryMarker.ORDER_BY_MODE, "ASC");
                int rtResult = rtdao.selectResourceTypesByParentId(InfoAggregator.getInstance().getResourceTypeId("post"));
                if (rtResult > 0) {
                    List<ResourceType> resourceTypes = new ArrayList<ResourceType>();
                    if (secondaryResource != null && secondaryResource.isEmpty() == false) {
                        // select the specific resource type.
                        for (ResourceType rt : rtdao.getList()) {
                            if (InfoAggregator.getInstance().getResourceTypeId(secondaryResource) == rt.getId()) {
                                resourceTypes.add(rt);//
                                resourceTypeId = rt.getId();// set the id so the post dao selector knows what to look for.
                                correctResourceType = true;
                            }
                        }
                        if (correctResourceType == false) {
                            resourceTypes = rtdao.getList();
                        }
                    } else {
                        resourceTypes = rtdao.getList();
                    }
                    request.setAttribute("resourceTypes", resourceTypes);
                } else {
                    Logger.getLogger(TaskController.class.getName()).log(Level.WARNING, "No resource type was detected for tasks.");
                }
                if (id > 0) {
                    // mark the specific resource - likely with the derived value marker. so the jsp can preselect it
                }
                // select task actions
                int taResult;
                if (correctResourceType == true) {
                    taResult = InfoAggregator.getInstance().getTaskActionTypes().size();
                    tadao.setBeans(InfoAggregator.getInstance().getTaskActionTypes());
                } else {// only select the "Create" action as that one doesn't require a post id.
                    taResult = tadao.selectBeanById(InfoAggregator.getInstance().getTaskActionTypeId("create"));
                }
                Logger.getLogger(TaskController.class.getName()).log(Level.INFO, "taResult is:{0}", taResult);
                if (taResult > 0) {
                    request.setAttribute("actions", tadao.getBeans());
                } else {
                    Logger.getLogger(TaskController.class.getName()).log(Level.WARNING, "No action type was detected for tasks.");
                }

                // select resources
                if (correctResourceType == true) {
                    CsdwaDao dao = new DaoFactory(conn).getDao(resourceTypeId);
                    dao.setQueryMarkerValue(QueryMarker.ORDER_BY_COLUMN, "name");
                    dao.setQueryMarkerValue(QueryMarker.ORDER_BY_MODE, "ASC");

                    if (id != 0) {
                        // send the the resource id the task was created from. The view must handle the actual selection logic.
                        request.setAttribute("selectedId", id);
                    }
                    int result = dao.selectBeans();

                    if (result > 0) {
                        request.setAttribute("posts", dao.getBeans());
                    } else {
                        Logger.getLogger(TaskController.class.getName()).log(Level.WARNING, "No posts were detected for tasks");
                    }
                    request.setAttribute("taskResourceTypeName", secondaryResource);
                } else {
                    Logger.getLogger(TaskController.class.getName()).log(Level.INFO, "Couldn't determine resource type so don't attept to get posts or set posts attribute");
                }
            } else if ("delete".equalsIgnoreCase(primaryAction)) {

                TaskDao tdao = new TaskDao(conn);
                int result = tdao.selectBeanById(id);

                if (result > 0) {
                    request.setBean(tdao.getBean());
                    request.setView(VIEW_DELTE);
                } else {
                    request.setMessage("The task couldn't be found.");
                    request.setMessageType("error");
                    request.setPrimaryActionTypeName("list");
                    request.setPrimaryResourceTypeName("task");
                    request.setPrimaryResourceId(0);
                    request = handleGetRequest(request);
                    Logger.getLogger(TaskController.class.getName()).log(Level.SEVERE, "No task found in the database.");
                }
            } else if ("start".equalsIgnoreCase(primaryAction)) {
                // start a task and thus track it.

                TaskActionDao tadao = new TaskActionDao(conn);
                TaskDao tdao = new TaskDao(conn);
                int result = tdao.selectBeanById(id);
                if (result > 0) {
                    Task task = tdao.getBean();

                    tadao.selectBeanById(task.getActionTypeId());
                    TaskAction taskAction = tadao.getBean();
                    request.setPrimaryActionTypeName(InfoAggregator.getInstance().getActionTypeName(taskAction.actionTypeId));
                    request.setPrimaryResourceTypeName(InfoAggregator.getInstance().getResourceTypeName(task.getPostResourceTypeId()));
                    request.setPrimaryResourceId(task.getPostId());
                    TaskManager taskManager = this.ResourceRequestDelegator.getTaskManager();
                    taskManager.trackTask(id, request.getCurrentUser().getId(), "starttask");
                    this.getResourceRequestDelegator().getUserHistoryManager().recordUserPostHistory(request.getCurrentUser(), InfoAggregator.getInstance().getTaskActionTypeId("starttask"), task);
                    request = this.ResourceRequestDelegator.processRequest(request);
                } else {
                    request.setMessage("The task couldn't be found.");
                    request.setMessageType("error");
                    request.setPrimaryActionTypeName("list");
                    request.setPrimaryResourceTypeName("task");
                    request.setPrimaryResourceId(0);
                    Logger.getLogger(TaskController.class.getName()).log(Level.SEVERE, "No task found in the database.");
                    request = handleGetRequest(request);
                }

            } else {// index by default
                request.setView(VIEW_LIST);
                String headingTitle; // Title of the page the jsp will display
                TaskDao tdao = new TaskDao(conn);
                tdao.setUseRelatedMarkers(true);
                int result;
                if (secondaryAction != null && "list-assigned".equalsIgnoreCase(secondaryAction)) {
                    headingTitle = "Tasks I Assigned";
                    result = tdao.selectTasksByCreator(request.getCurrentUser().getId());
                } else {
                    // by default just list pending tasks.
                    headingTitle = "My Pending Tasks";
                    result = tdao.selectTasksByStatus(request.getCurrentUser().getId(), InfoAggregator.getInstance().getStateTypeId("completed"), false);// select tasks that aren't "in the completed state"
                    request.setAttribute("listType", "pending");
                }
                if (result <= 0) {
                    request.setMessage("There are no tasks to list.");
                    request.setMessageType("info");
                }
                request.setBeans(tdao.getBeans());
                request.setAttribute("headingTitle", headingTitle);
            }
        }
        return request;
    }

    /**
     * This method saves the bean to the database.
     *
     * @param request the create request object containing the form values.
     * @return the request object with the requestResultMessage object set to
     * indicate the result
     */
    private CsdwaRequest handleCreate(CsdwaRequest request) {
        User user = (User) request.getSessionAttribute("user");
        Timestamp timestamp = new Timestamp(new Date().getTime());
        // Create validators to use for validation
        CsdwaValidator isNotEmpty = new IsNotEmpty();
        CsdwaValidator isNumeric = new IsNumeric();
        // add request parameter values to the object to test validation
        ValidatorValue name = new ValidatorValue(request.getParameter("name"), "Name");
        ValidatorValue description = new ValidatorValue(request.getParameter("description"), "Description");
        ValidatorValue subjectId = new ValidatorValue(request.getParameter("postId"), "Subject ID");
        ValidatorValue action = new ValidatorValue(request.getParameter("action"), "Task Action");
        ValidatorValue resourceType = new ValidatorValue(request.getParameter("resourceType"), "Subject Resource Type");
        ValidatorValue recipient = new ValidatorValue(request.getParameter("recipient"), "Recipient");
        // the the validators to the validation values
        name.addValidator(isNotEmpty);
        description.addValidator(isNotEmpty);
        subjectId.addValidator(isNumeric).addValidator(isNotEmpty);
        action.addValidator(isNumeric).addValidator(isNotEmpty);
        resourceType.addValidator(isNumeric).addValidator(isNotEmpty);
        recipient.addValidator(isNumeric).addValidator(isNotEmpty);
        // isValid the values.
        if (name.isValid() && description.isValid() && action.isValid() && resourceType.isValid() && recipient.isValid()) {
            // the post id is not needed for Create New type tasks and isn't thus set on the form.
            if (subjectId.isValid() == false) {
                subjectId.setValue(0);
            }
            Task bean = ResourceFactory.makeTask();
            bean.setName(name.getString());
            bean.setDescription(description.getString());
            bean.setActionTypeId(action.getInteger());
            bean.setPostResourceTypeId(resourceType.getInteger());
            bean.setPostId(subjectId.getInteger());
            bean.setRecipientId(recipient.getInteger());
            bean.setUpdatedById(user.getId());
            bean.setCreatedById(user.getId());
            bean.setCreatedDate(timestamp);
            bean.setUpdatedDate(timestamp);
            bean.setStatusDate(timestamp);
            bean.setStateId(InfoAggregator.getInstance().getStateTypeId("unread"));

            TaskDao tdao = new TaskDao(conn);
            int result = tdao.insertBean(bean);
            if (result > 0) {
                bean.setId(result);
                request.setMessage("The task was successfully created.");
                request.setMessageType("success");
                this.getResourceRequestDelegator().getUserHistoryManager().recordUserPostHistory(request.getCurrentUser(), InfoAggregator.getInstance().getActionTypeId("create"), bean);
                request.setPrimaryActionTypeName("view");
                request.setPrimaryResourceTypeName("task");
                request.setPrimaryResourceId(result);
                request = handleGetRequest(request);
            } else {
                request.setMessage("There was an error saving the task to the database.");
                request.setMessageType("error");
                request.setPrimaryActionTypeName("create");
                request.setPrimaryResourceTypeName("task");
                request.setPrimaryResourceId(0);
                request = handleGetRequest(request);
            }
        } else {
            request.setMessage(name.getErrorMessage() + description.getErrorMessage() + action.getErrorMessage() + resourceType.getErrorMessage() + recipient.getErrorMessage());
            request.setMessageType("info");
            request.setPrimaryActionTypeName("create");
            request.setPrimaryResourceTypeName("task");
            request.setPrimaryResourceId(0);
            request = handleGetRequest(request);
        }
        return request;
    }

    /**
     * This method handles updating the bean. If the bean to be updated was
     * properly set as a bean in the session, then this method will instruct the
     * dao to update only the fields that were changed and thus reduce message
     * size.
     *
     * @param request the update request object
     * @return a request object with the requestResultMessage object set to
     * indicate the update result.
     */
    private CsdwaRequest handleUpdate(CsdwaRequest request) {
        User user = (User) request.getSessionAttribute("user");
        Timestamp timestamp = new Timestamp(new Date().getTime());
        Task originalPost = (Task) request.getSessionAttribute("bean");

        CsdwaValidator isNotEmpty = new IsNotEmpty();
        CsdwaValidator isNumeric = new IsNumeric();
        // add request parameter values to the object to test validation
        ValidatorValue name = new ValidatorValue(request.getParameter("name"), "Name");
        ValidatorValue description = new ValidatorValue(request.getParameter("description"), "Description");
        ValidatorValue id = new ValidatorValue(request.getParameter("id"), "Subject ID");
        ValidatorValue action = new ValidatorValue(request.getParameter("action"), "Task Action");

        // the the validators to the validation values
        name.addValidator(isNotEmpty);
        description.addValidator(isNotEmpty);
        id.addValidator(isNumeric).addValidator(isNotEmpty);
        action.addValidator(isNumeric).addValidator(isNotEmpty);
        // isValid the values.
        if (name.isValid() && description.isValid() && id.isValid() && action.isValid()) {
            TaskDao tdao = new TaskDao(conn);
            StringBuilder sb = new StringBuilder();
            if (originalPost == null) {
                originalPost = ResourceFactory.makeTask();// if the original post didn't get stored in the session, then create a new one.
            }

            if (originalPost.getName().equals(name.getString()) == false) {
                sb.append("Name");
            }
            if (originalPost.getDescription().equals(description.getString()) == false) {
                if (sb.length() > 0) {
                    sb.append(", ");
                }
                sb.append("Description");
            }
            if (originalPost.getActionTypeId() == action.getInteger()) {
                if (sb.length() > 0) {
                    sb.append(", ");
                }
                sb.append("ActionTypeID");
            }

            // always set updated by id
            if (sb.length() > 0) {
                sb.append(", ");
            }
            sb.append("UpdatedByID");
            // always set updated date
            if (sb.length() > 0) {
                sb.append(", ");
            }
            sb.append("UpdatedDate");

            // update the original bean with all the values from the form submit
            originalPost.setName(name.getString());
            originalPost.setDescription(description.getString());
            originalPost.setUpdatedById(user.getId());
            originalPost.setUpdatedDate(timestamp);

            // declare the columns to update.
            tdao.setColumnsToUpdate(sb.toString());

            int result = tdao.updateBean(originalPost);
            request.removeSessionAttribute("bean");
            if (result > 0) {
                this.getResourceRequestDelegator().getUserHistoryManager().recordUserPostHistory(request.getCurrentUser(), InfoAggregator.getInstance().getActionTypeId("update"), originalPost);
                // success so go to view page of updated plot
                request.setMessage("The task was successfully updated.");
                request.setMessageType("success");
                request.setPrimaryActionTypeName("view");
                request.setPrimaryResourceTypeName("task");
                request.setPrimaryResourceId(originalPost.getId());
                request = handleGetRequest(request);
            } else {
                request.setMessage("There was an error trying to update the task to the database.");
                request.setMessageType("error");
                request.setPrimaryActionTypeName("update");
                request.setPrimaryResourceTypeName("task");
                request.setPrimaryResourceId(originalPost.getId());
                request = handleGetRequest(request);
            }

        } else {
            request.setMessage(name.getErrorMessage() + description.getErrorMessage() + id.getErrorMessage() + action.getErrorMessage());
            request.setMessageType("info");
            request.setPrimaryActionTypeName("update");
            request.setPrimaryResourceTypeName("task");
            request.setPrimaryResourceId(originalPost.getId());
            request = handleGetRequest(request);
        }

        return request;
    }

    /**
     * This method handles deleting the specific bean from the database. This
     * method handles the cascading deletes of all dependant resources.
     *
     * @param request the delete request object.
     * @return a request object with a requestResultMessage object specifying
     * the result.
     */
    private CsdwaRequest handleDelete(CsdwaRequest request) {
        // validate fields
        int id = request.getPrimaryResourceId();
        boolean allowDelete = false;
        if (id > 0) {
            try {
                // set auto commit off for connection
                Connection conn = this.conn;
                conn.setAutoCommit(false);// enter transaction mode
                Savepoint delete = conn.setSavepoint("delete-task_" + id);
                TaskDao tdao = new TaskDao(conn);
                if (tdao.selectBeanById(id) > 0) {
                    allowDelete = true;
                }
                if (allowDelete == true) {
                    Task task = tdao.getBean();
                    int result = tdao.deleteBean(task);
                    request.removeAttribute("formSubmit");
                    if (result > 0) {
                        this.getResourceRequestDelegator().getUserHistoryManager().recordUserPostHistory(request.getCurrentUser(), InfoAggregator.getInstance().getActionTypeId("delete"), task);
                        conn.setAutoCommit(true);// reset commit mode to auto and commit the transaction since the deletion worked correctly.
                        request.setMessage("The task was successfully deleted from the site.");
                        request.setMessageType("success");
                        request.setPrimaryActionTypeName("list");
                        request.setPrimaryResourceTypeName("task");
                        request.setPrimaryResourceId(0);
                        request = handleGetRequest(request);
                    } else {
                        conn.rollback(delete); // delete failed so roll back connection
                        conn.setAutoCommit(true);// reset commit mode to auto
                        request.setMessage("There was an error trying to delete the task from the database.");
                        request.setMessageType("error");
                        request.setPrimaryActionTypeName("view");
                        request.setPrimaryResourceTypeName("task");
                        request.setPrimaryResourceId(id);
                        request = handleGetRequest(request);
                    }

                } else {
                    conn.rollback(delete); // delete failed so roll back connection
                    conn.setAutoCommit(true);// reset commit mode to auto
                    request.setMessage("A dependant resource could not be deleted. Deletion aborted.");
                    request.setMessageType("error");
                    request.setPrimaryActionTypeName("view");
                    request.setPrimaryResourceTypeName("plot");
                    request.setPrimaryResourceId(0);
                    request = handleGetRequest(request);
                }

            } catch (SQLException ex) {
                Logger.getLogger(TaskController.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            request.setMessage("There was an error with the delete request. Could not determine the task ID.");
            request.setMessageType("error");
            request.setPrimaryActionTypeName("list");
            request.setPrimaryResourceTypeName("task");
            request.setPrimaryResourceId(0);
            request = handleGetRequest(request);
        }
        return request;
    }
}
